use std::collections::HashMap;
use std::fmt::Error;
use std::fs;
use std::path::Path;

pub fn check_id_card(id_card_no: &str) -> String {
    let mut id_card_no = id_card_no.to_string();

    if !is_valid_id(&mut id_card_no) {
        return "身份证号码错误".to_string();
    }

    let local;
    match get_local(&id_card_no) {
        Ok(s) => {
            local = s
        }
        Err(_) => {
            return "身份证号码错误".to_string();
        }
    }
    let brith_day = get_birthday(&id_card_no);
    let sex =
        if is_male(&id_card_no) {
            "男"
        } else {
            "女"
        };
    format!("{},{},{},{}", "身份证号码正确", sex, brith_day, local)
}

fn generate_local_map() -> HashMap<String, String> {
    let path = Path::new("data.json");
    let content = fs::read_to_string(path).unwrap();
    serde_json::from_str(content.as_str()).unwrap()
}

fn is_male(id_card_no: &str) -> bool {
    let last = &id_card_no[14..];
    let sex_num = last.chars().nth(2).unwrap();
    let sex_num = sex_num.to_digit(10);
    sex_num.unwrap() & 1 == 1
}

fn get_birthday(id_card_no: &str) -> String {
    let brith_day = &id_card_no[6..14];
    format!("{}年{}月{}日", &brith_day[0..4], &brith_day[4..6], &brith_day[6..])
}

fn get_local(id_card_no: &str) -> Result<String, Error> {
    let local_map = generate_local_map();
    let local = &id_card_no[0..6];
    let city = (&local[0..2].to_string()).to_string() + "0000";
    let jurisdiction = (&local[0..4].to_string()).to_string() + "00";
    let district = local.to_string();
    let mut local = vec![];

    if let Some(&ref city) = local_map.get(city.as_str()) {
        local.push(city.to_string());
    }

    let city = &local[0];
    if let Some(&ref jurisdiction) = local_map.get(jurisdiction.as_str()) {
        if let Some(index) = jurisdiction.find(city) {
            let result = [&jurisdiction[..index], &jurisdiction[index + city.len()..]].concat();
            local.push(result.to_string());
        }
    }

    let jurisdiction = &local[1];
    if let Some(&ref district) = local_map.get(district.as_str()) {
        let city = &local[0];
        let delete_city = &district[city.len()..];
        let delete_jurisdiction = &delete_city[0..jurisdiction.len()];
        let district =
            if jurisdiction.eq(delete_jurisdiction) {
                &delete_city[jurisdiction.len()..]
            } else {
                &delete_city
            };
        local.push(district.to_string());
    } else {
        return Err(Error);
    }

    local.retain(|c| c.chars().count() > 1);
    Ok(local.join("-"))
}

fn is_valid_id(id_card_no: &mut String) -> bool {
    if id_card_no.len() == 15 {
        id_card_no.insert_str(6, "19");
        return true;
    }

    if id_card_no.len() != 18 {
        return false;
    }

    if !id_card_no[..17].chars().all(|c| c.is_digit(10))
        || !id_card_no.chars().last().map(|c| c.is_digit(10) || c == 'X')
        .unwrap_or(false) {
        return false;
    }

    let weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];

    let check_codes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
    let sum: u32 = id_card_no[..17]
        .chars()
        .zip(weights.iter())
        .map(|(c, &w)| c.to_digit(10).unwrap() * w as u32)
        .sum();

    let check_index = (sum % 11) as usize;
    let expected_check_code = check_codes[check_index];

    id_card_no.chars().last().unwrap() == expected_check_code
}